"""量化模型基类."""

from __future__ import annotations

import logging
from abc import ABC, abstractmethod
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Dict, Optional

import numpy as np
import pandas as pd

logger = logging.getLogger(__name__)


@dataclass
class ModelConfig:
    """模型配置参数."""

    name: str
    random_seed: int = 42
    params: Dict[str, Any] | None = None

    def __post_init__(self) -> None:
        if self.params is None:
            self.params = {}


class BaseQuantModel(ABC):
    """量化模型抽象基类."""

    def __init__(self, config: ModelConfig) -> None:
        self.config = config
        self._is_trained = False
        self._feature_names: list[str] | None = None
        self._training_summary: Dict[str, Any] | None = None  # 保存训练摘要信息

    @property
    def is_trained(self) -> bool:
        """模型是否已训练."""
        return self._is_trained

    @property
    def feature_names(self) -> list[str] | None:
        """特征名称列表."""
        return self._feature_names

    @abstractmethod
    def train(
        self,
        X_train: pd.DataFrame | np.ndarray,
        y_train: pd.Series | np.ndarray,
        X_val: pd.DataFrame | np.ndarray | None = None,
        y_val: pd.Series | np.ndarray | None = None,
    ) -> Dict[str, Any]:
        """训练模型.

        Parameters
        ----------
        X_train:
            训练特征数据
        y_train:
            训练标签（二分类：1=上涨，0=下跌）
        X_val:
            验证特征数据（可选）
        y_val:
            验证标签（可选）

        Returns
        -------
        训练过程摘要字典，包含：
        - loss_history: 损失历史（如适用）
        - val_metrics: 验证集指标（如适用）
        - feature_importance: 特征重要性（如适用）
        """
        pass

    @abstractmethod
    def predict_proba(self, X: pd.DataFrame | np.ndarray) -> np.ndarray:
        """预测上涨概率.

        Parameters
        ----------
        X:
            特征数据

        Returns
        -------
        上涨概率数组，shape=(n_samples,)，值域 [0, 1]
        """
        pass

    def predict(self, X: pd.DataFrame | np.ndarray, threshold: float = 0.5) -> np.ndarray:
        """预测类别（基于概率阈值）.

        Parameters
        ----------
        X:
            特征数据
        threshold:
            分类阈值，默认 0.5

        Returns
        -------
        预测类别数组，1=上涨，0=下跌
        """
        proba = self.predict_proba(X)
        return (proba >= threshold).astype(int)

    @abstractmethod
    def save(self, path: str | Path) -> None:
        """保存模型到文件.

        Parameters
        ----------
        path:
            保存路径
        """
        pass

    @abstractmethod
    def load(self, path: str | Path) -> None:
        """从文件加载模型.

        Parameters
        ----------
        path:
            模型文件路径
        """
        pass

    def get_feature_importance(self) -> Dict[str, float] | None:
        """获取特征重要性（如适用）.

        Returns
        -------
        特征重要性字典，键为特征名，值为重要性分数。如果模型不支持，返回 None。
        """
        return None

    def _validate_trained(self) -> None:
        """验证模型是否已训练."""
        if not self._is_trained:
            raise RuntimeError(f"模型 {self.config.name} 尚未训练，请先调用 train() 方法")

    def _set_feature_names(self, X: pd.DataFrame | np.ndarray) -> None:
        """设置特征名称."""
        if isinstance(X, pd.DataFrame):
            self._feature_names = list(X.columns)
        else:
            self._feature_names = [f"feature_{i}" for i in range(X.shape[1])]

